# -*- coding: utf-8 -*-

# $Id: files.py 2 2012-09-01 15:54:17Z alexis.roda.villalonga@gmail.com $

import codecs
import os
import re


class _LazyLoader(object):
    """Lazy file reader.

    This class implements a non-data descriptor that allows a
    'TextFile' object to postpone the loading of the file contents
    until it is first accessed.
    """
    def __get__(self, obj, objtype):
        if obj is None:
            return self
        try:
            obj._lines = codecs.open(obj._path, "r", obj._encoding).readlines()
        except IOError:
            obj._lines = []
        return obj._lines


class TextFile(object):
    """A class representing a text file.

    This class provide utility methods intended to manipulate the
    contents of a text file.
    """

    _lines = _LazyLoader()

    def __init__(self, path, encoding="utf-8"):
        """Constructor.

        :param path:

        :raises:
        :returns:
        :rtype:
        """
        self._path = os.path.abspath(path)
        self._encoding = encoding

    @property
    def content(self):
        """Contents of the file.
        """
        return "".join(self._lines)

    def save(self, make_backup=False):
        """Save changes to disk.

        :param make_backup:

        :raises:
        :returns: None
        :rtype:
        """
        if make_backup:
            backup_name = self._path + ".bak"
            if os.path.exists(backup_name):
                os.remove(backup_name)
            os.rename(self._path, backup_name)
        with codecs.open(self._path, "w", self._encoding) as f:
            f.writelines(self._lines)

    def remove(self):
        """Delete the file from the filesystem.
        """
        os.remove(self._path)

    def insert_before(self, text, marker, indent=True):
        """Insert text before marker

        :param text: a list of unicode string
        :param marker:
        :param indent:

        :raises:
        :returns: None
        :rtype:
        """
        self._insert(text, marker, indent, "before")

    def insert_after(self, text, marker, indent=True):
        """Insert text after marker.

        :param text:
        :param marker:
        :param indent:

        :raises:
        :returns: None
        :rtype:
        """
        self._insert(text, marker, indent, "after")

    def find_line_containing(self, regexp, start=0):
        """Finds a line containing a regular expressions.

        :param regexp:
        :param start:

        :raises:
        :returns: the line index, starting from 0, None is no match is
                  found
        :rtype: int
        """
        for index, line in enumerate(self._lines):
            if index < start:
                continue
            if regexp.search(line):
                return index
        return None

    def find_line_matching(self, regexp, start=0):
        """Finds a line matching a regular expressions.

        :param regexp:
        :param start:

        :raises:
        :returns: the line index, starting from 0, None is no match is
                  found
        :rtype: int
        """
        for index, line in enumerate(self._lines):
            if index < start:
                continue
            if regexp.match(line):
                return index
        return None

    def _insert(self, text, marker, indent, where):
        """Inserts text relative to marker.

        :param text:
        :param marker:
        :param ident:
        :param where:

        :raises:
        :returns:
        :rtype:
        """
        for line in text:
            if not line.endswith("\n"):
                raise ValueError("lines must end with \\n")
        regex = re.compile(r"-\*- %s -\*-" % marker)
        index = self.find_line_containing(regex, 0)
        if index is None:
            raise ValueError(
                "marker '-*- %s -*-' not found in file '%s'" % (
                    marker,
                    os.path.relpath(self._path)
                    )
                )
        if indent:
            line = self._lines[index]
            unindented_line = line.lstrip()
            indentation = " " * (len(line) - len(unindented_line))
            text[:] = [indentation + i for i in text]
        if where == "before":
            self._lines[index:index] = text
        else:
            self._lines[index + 1:index + 1] = text


class PythonModule(TextFile):
    """Python module file.
    """

    def insert_import(self, import_):
        pass
